SPB Git forge
28commits 1branches 0releases
7.7 MBsize
maindefault branch
10 days agolast push
Python 66.3% TypeScript 22.7% JavaScript 8.6% HTML 1.4% CSS 0.7%
4.2 KB · 101 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import Link from 'next/link';3import { notFound } from 'next/navigation';4import { DiffViewer } from '@/components/events/diff-viewer';5import { EventList } from '@/components/events/event-row';6import { SignificanceBadge } from '@/components/ui/badges';7import { KV, Row } from '@/components/ui/key-value';8import { Container, Note } from '@/components/ui/section';9import { api, ApiError } from '@/lib/api';10import { fmtDateTime, fmtInt, fmtPct } from '@/lib/format';11import { routes, SURFACE_LABELS } from '@/lib/site';1213export const revalidate = 600;14export const metadata: Metadata = { title: 'Change', robots: { index: false } };1516export default async function ChangePage({ params }: { params: Promise<{ id: string }> }) {17  const { id } = await params;18  let ch;19  try {20    ch = await api.change(id);21  } catch (e) {22    if (e instanceof ApiError && e.notFound) notFound();23    throw e;24  }25  return (26    <Container>27      <div className="pb-4 pt-6 md:pt-10">28        <p className="eyebrow flex flex-wrap items-center gap-2">29          Change · {SURFACE_LABELS[ch.surface] ?? ch.surface}30          <SignificanceBadge value={ch.significance} />31        </p>32        <h1 className="display mt-2 text-[24px] md:text-[32px]">33          {ch.company ? (34            <Link href={routes.company(ch.company.slug)} className="hover:text-accent">35              {ch.company.display_name}36            </Link>37          ) : (38            'Monitored page'39          )}{' '}40          <span className="text-ink-3">·</span> {SURFACE_LABELS[ch.surface] ?? ch.surface} changed41        </h1>42        <p className="mt-2 text-sm text-ink-2">43          Detected {fmtDateTime(ch.detected_at)} · kind <span className="mono">{ch.kind}</span> · text delta {fmtPct(ch.text_delta_ratio, 1, true)}44          {ch.similarity !== null ? ` · similarity ${fmtPct(ch.similarity, 1, true)}` : ''}45        </p>46      </div>47      <div className="grid gap-8 lg:grid-cols-12">48        <div className="lg:col-span-8">49          <DiffViewer diff={ch.diff} significance={ch.significance} />50        </div>51        <aside className="space-y-6 lg:col-span-4">52          <KV>53            <Row k="Sensor">54              <Link href={routes.sensor(ch.sensor_id)} className="link mono text-xs break-all">55                {ch.sensor_id}56              </Link>57            </Row>58            <Row k="Before">59              {ch.snapshot_before ? (60                <Link href={routes.snapshot(ch.snapshot_before)} className="link mono text-xs break-all">61                  {ch.snapshot_before}62                </Link>63              ) : (64                <span className="text-ink-3">first observation</span>65              )}66            </Row>67            <Row k="After">68              <Link href={routes.snapshot(ch.snapshot_after)} className="link mono text-xs break-all">69                {ch.snapshot_after}70              </Link>71            </Row>72            {ch.snapshot_before && (73              <Row k="Full diff">74                <Link href={routes.snapshotDiff(ch.snapshot_before, ch.snapshot_after)} className="link text-xs">75                  Snapshot diff viewer →76                </Link>77              </Row>78            )}79            <Row k="Blocks">80              <span className="tnum">81                +{fmtInt(ch.blocks_added)} · −{fmtInt(ch.blocks_removed)} · ~{fmtInt(ch.blocks_modified)}82              </span>83            </Row>84          </KV>85          {Object.keys(ch.structured_delta ?? {}).length > 0 && (86            <div>87              <p className="eyebrow mb-1">Structured delta</p>88              <pre className="overflow-x-auto border border-rule bg-surface-2 p-3 text-xs">{JSON.stringify(ch.structured_delta, null, 2)}</pre>89            </div>90          )}91          <div>92            <p className="eyebrow mb-1">Events derived from this change</p>93            <EventList events={ch.events ?? []} variant="table" showCompany={false} emptyLabel="No structured event was derived from this change (below the significance threshold or awaiting processing)." />94          </div>95          <Note>Raw observations, normalised snapshots, diffs and events are stored separately; this diff can be recomputed from the two snapshots without re-fetching the page.</Note>96        </aside>97      </div>98    </Container>99  );100}101